 /*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.hemerasolutions.v2trainer.dao;


import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceException;
import javax.persistence.criteria.*;

import org.hibernate.exception.ConstraintViolationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.hemerasolutions.v2trainer.dao.interfaces.BaseDaoInterface;
import com.hemerasolutions.v2trainer.model.interfaces.BaseModelInterface;
import com.hemerasolutions.v2trainer.model.interfaces.BaseReferencedModelInterface;
import com.hemerasolutions.v2trainer.model.screen.Reference;
import com.hemerasolutions.v2trainer.util.annotations.ReferencedModel;


/**
 * @author ryegros
 * Basis operations of DAO, used to manipulate the database
 * object 
 */


public class BaseDao<T> implements BaseDaoInterface<T> {

	private static final long serialVersionUID = 1L;

	@PersistenceContext
	private EntityManager em;
	
    private final Class<T> oClass;
    
    @Autowired
	protected MessageSource message;
	
    @SuppressWarnings("unchecked")
	public BaseDao(){
        this.oClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
    }

  	@Override
    public Class<T> getObjectClass(){
    	return this.oClass;
    }

  	
  	@Override
    @Transactional(readOnly = true)
  	public T getById(int id) throws ExceptionDao {
  	
  		return (T) this.em.find(oClass, id);  	

   	}
  	
  	
  	@Override
  	public void detach(T object){
  		this.em.detach(object);
  	}
  	
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  	@Override
    public T create(T object)throws ExceptionDao{
    	
    	try {
			em.persist(object);
		}catch(PersistenceException e){
			if(e.getCause().getClass() == ConstraintViolationException.class){
				// Duplicated Constraint
				throw new ExceptionDao(this.localizedText("AlreadyExist"),e,3);
			}else{
				// Other error
				e.printStackTrace();	
				throw new ExceptionDao(this.localizedText("NotPossibleToSave"),e,4);
			}
		}
    	catch (Exception e) {
    		// Other error
			e.printStackTrace();	
    		throw new ExceptionDao(e.getMessage(),e,999);
		}

    	return object;
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  	@Override
    public T save(T object)throws ExceptionDao{

    	try {
    		return em.merge(object);
		}catch(PersistenceException e){
			if(e.getCause().getClass() == ConstraintViolationException.class){
				// Duplicated Constraint
				throw new ExceptionDao(this.localizedText("AlreadyExist"),e,3);
			}else{
				// Other error
				e.printStackTrace();	
				throw new ExceptionDao(this.localizedText("NotPossibleToSave"),e,4);
			}
		}catch (Exception e) {
    		// Outro erro
			throw new ExceptionDao(e.getMessage(),e,999);
    	}
    }    

    
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  	@Override
    public List<T> create(List<T> objects)throws ExceptionDao{
    	
    	List<T> returnList = new ArrayList<T>();

    	try {
    		for(T obj:objects){
    			returnList.add(em.merge(obj));
    		}	
    	}catch(PersistenceException e){
			if(e.getCause().getClass() == ConstraintViolationException.class){
				// Duplicated Constraint
				throw new ExceptionDao(this.localizedText("AlreadyExist"),e,3);
			}else{
				// Other error
				throw new ExceptionDao(this.localizedText("NotPossibleToSave"),e,4);
			}
		}
    	catch (Exception e) {
    		// Other error
			throw new ExceptionDao(e.getMessage(),e,999);
		}

    	return objects;
    }

    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  	@Override
    public List<T> save(List<T> objects)throws ExceptionDao{

    	List<T> returnList = new ArrayList<T>();

    	try {
    		for(T obj:objects){
    			returnList.add(em.merge(obj));
    		}	
    		
    		return returnList;
		}catch(PersistenceException e){
			throw new ExceptionDao(this.localizedText("NotPossibleToSave"),e,4);
		}
    	catch (Exception e) {
    		// Outro erro
			throw new ExceptionDao(e.getMessage(),e,999);
    	}
    }    
        
    
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
  	@Override
    public void delete(T object)throws ExceptionDao{
    	try{
    		this.em.remove(this.em.merge(object));
    	}catch(Exception e){
			throw new ExceptionDao(e.getMessage(),e,999);
    	}
    }

    @Transactional(readOnly = true)
  	@Override
    public List<T> list(){

    	List<T> result = null;
    	
    	CriteriaBuilder cb = em.getCriteriaBuilder();

    	CriteriaQuery<T> cq = cb.createQuery(oClass);

    	Root<T> root = cq.from(oClass);
  	  
    	cq.select(root);
    	
		result = em.createQuery(cq).getResultList();
        
       	return result;
    }
    
    @Transactional(readOnly = true)
  	@Override
    public T getByName(String name) {
		
		CriteriaBuilder cb = this.getEntityManager().getCriteriaBuilder();

		CriteriaQuery<T> cq = cb.createQuery(oClass);
		
		Root<T> object = cq.from(oClass);
		
		List<T> result = new ArrayList<T>();

	    Predicate pred;
	    	
		pred = cb.equal(object.<String>get("name"), name);

		cq.where(pred);
	 
		result =  em.createQuery(cq).setMaxResults(1).getResultList();
		
		if(!result.isEmpty()){
			return (T)result.get(0);
		}else{
			return null;
		}
    
    }
    
  	@Override
	public void setEntityManager(EntityManager em) {
		this.em = em;
	}

	@Override
	public EntityManager getEntityManager(){
		return this.em;
	}

	
	@Override
	public Date endDayDateHour(Date dt) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(dt);
		cal.set(Calendar.HOUR_OF_DAY, 23);
		cal.set(Calendar.MINUTE, 59);
		cal.set(Calendar.SECOND, 59);
		
		return cal.getTime();
	}

	@Override
	public Date begDayDateHour(Date dt) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(dt);
		cal.set(Calendar.HOUR_OF_DAY, 00);
		cal.set(Calendar.MINUTE, 00);
		cal.set(Calendar.SECOND, 00);
		
		return cal.getTime();
	}

	
	@Override
	public String localizedText(String id){

		return this.message.getMessage(id,null,null);

	}
	
	@Override
	public MessageSource getMessage() {
		return message;
	}

	@Override
	public void setMessage(MessageSource message) {
		this.message = message;
	}

	@Override
	public long getSerialversionuid() {
		return serialVersionUID;
	}    
	
	
    @Transactional(readOnly = true)
  	@Override	
	public boolean hasChanged(T object){
		
		if(object instanceof BaseModelInterface){
			return ((BaseModelInterface) object).hasChanged(this.getById(((BaseModelInterface) object).getId()));
		}
		
		return false;
	}
	
    @Transactional(readOnly = true)
  	@Override
  	public List<Reference> getReferences(T object ){
		List<Reference> references = new ArrayList<Reference>();

		for(Field field: this.getObjectClass().getDeclaredFields()){
			if(field.isAnnotationPresent(ReferencedModel.class)){
				try {
					
					field.setAccessible(true);

					if(field.getType().equals(List.class)){
						@SuppressWarnings("rawtypes")
						List itens = (java.util.List)field.get(object);

						for(int x = 0;x < itens.size();x++){
							BaseReferencedModelInterface usedBy = (BaseReferencedModelInterface)itens.get(x);
							
							Reference ref = new Reference((BaseReferencedModelInterface)object,usedBy);
							references.add(ref);
						}
						
					}else{
						
						BaseReferencedModelInterface usedBy = (BaseReferencedModelInterface)field.get(object);
						
						Reference ref = new Reference((BaseReferencedModelInterface)object,usedBy);
						references.add(ref);
					}
				
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}
					
		return references;
	}
	
	
}